/* tripl/tripl.c 
 * 
 * This file is part of tripl. 
 * 
 * tripl is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * tripl is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with tripl. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>

#include <klist.h>
#include <tripl/tripl.h>
#include <tripl/limits.h>
#include <tripl/arch.h>


char *linebuffer_save = NULL;
char *line_buffer = NULL;
int lno = 0;
char *eof;
char *line_eof;


static int next_line(char *), 
	   parse_line(void);


struct arch *arch_fn;
extern struct arch 	interp_arch_fn, 
       			x86_64_arch_fn;


struct context *current;

struct context main_context = {
	.funcname = "main", 
	.link = NULL, 
	.private_data = NULL
};


const static struct {
	char *name;
	struct arch *arch;
} arch_list[] = {
	{ "interp", &interp_arch_fn}, 
	{ "x86_64", &x86_64_arch_fn}, 
	{ NULL, NULL }
};


const struct attr_table vardef_attrtab[] = {
	{ "global", 1 }, 
	{ "rdonly", 4 }, 
	{ "ptr", 8 }, 
	{ NULL, 0 }
};



int do_tripl_compile(char *fbuffer, off_t fsize) {
	char *archname = getenv("TRIPL_ARCH");
	int i;

	current = &main_context;

	if (!archname) 
		arch_fn = &interp_arch_fn;

	else {
		for (i = 0; arch_list[i].name; i++) 
			if (!strcmp(arch_list[i].name, archname)) {
				arch_fn = arch_list[i].arch;

				goto ok;
			}
			

		fprintf(stderr, "*** unsupported arch %s (got from $TRIPL_ARCH). \n", archname);

		return 1;
	}

ok:	klist_init(&stack);

	eof = fbuffer + fsize;

	arch_fn->arch_init();

	while (next_line(fbuffer)) 
		parse_line();

	arch_fn->arch_fin ? arch_fn->arch_fin() : 0;

	return 0;
}


/* skip the comment, from '# asdf\nasdf2' to 'asdf2' */

static char *__skip_comment(char *fbuffer) {
	if (*fbuffer != '#') 
		return fbuffer;

	while (*fbuffer != '\n') 
		fbuffer++;

	_skip_spaces(fbuffer);

	if (*fbuffer == '#') 
		return __skip_comment(fbuffer);

	return fbuffer;
}


static int next_line(char *fbuffer) {
	char *p;
	
	++lno;

	/* first time we call this, initial */

	if (!line_buffer) {
		p = line_eof = line_buffer = fbuffer = __skip_comment(fbuffer);

		if (fbuffer >= eof) 
			/* only comment in this file */
			return 0;

		/* got line_eof */

		while (*p != ';') 
			p++, line_eof++;

		linebuffer_save = line_buffer;

		return 1;
	}

	/* move line_buffer to ';' + 1 and line_eof to line_buffer -> ';' */

	line_buffer = fbuffer = __skip_comment(line_buffer);

	if (fbuffer >= eof) 
		return 0;

	while (*line_buffer != ';') 
		line_buffer++;


	line_buffer++;

	/* \n */
	_skip_spaces(line_buffer)

	line_buffer = __skip_comment(line_buffer);

	line_eof = line_buffer;

	while (*line_eof != ';') 
		line_eof++;

	linebuffer_save = line_buffer;

	if (line_buffer >= eof) 
		return 0;

	return 1;
}



static int getnumber(char *p) {
	int dec = 0, log = 1, neg = 0;

	if (!isnr(*p)) {
		if (*p == '+') 
			neg = 0;

		else if (*p == '-') 
			neg = 1;

		else 
			exit(generic_error("a dec number required, but: ", linebuffer_save, lno));

		p++;
	}

	if (*p == '0' && isnr(p[1])) 
		exit(generic_error("number starting with '0'", linebuffer_save, lno));


	/* never use eof because this is calling from getword() */

	while (isnr(*p)) p++;

	--p;

	while (p >= line_buffer) {
		dec += itoa(*p) * log;
		log *= 10;

		p--;

		if (!isnr(*p)) 
			/* for - and + */
			break;
	}

	return neg ? -dec : dec;
}




/* NEVER MODIFY THIS! MAGIC! 
 * THIS IS REALLY THE UGLY CODE, MOST UGLY CODE ALL OVER THE WORLD. 
 *
 * BUT IT WORKS NOW, SO WE DO NOT TO CHANGE THIS AND WE WILL CHANGE 
 * A LOT BECAUSE ALL OF THE TRIPL REQUIRES THIS FUNCTION. 
 * */

#define isfirstchr() 	(!i)
#define notfirstchr() 	i


char *getword(char *fbuffer, int *type, int *out) {
	*type = WORDTYPE_INVAL;
	char *p;
	int i, in = 0;

	_skip_spaces(fbuffer);

	for (i = 0, p = fbuffer; p < line_eof; i++, p++) {
		if (*p == '\"') {
			if (in) {
				/* end */

				*out = i - 1;
				return fbuffer;
			}

			in = 1;
			*type = WORDTYPE_STR;
		}

		if (!in) {
			if (isnr(*p) || *p == '-' || *p == '+') {
				if (notfirstchr()) 
					/* name, but not a number */
					continue;

				/* get the number, and save to *out */

				*out = getnumber(p);

				/* line finished. */
				*type = WORDTYPE_NR;

				return p;
			}

			if (i == 1 && isspace(*p)) {
				if (ischr(p[-1])) {
					/* name */
					*type = WORDTYPE_NAME;
					*out = 1;

					return --p;
				}

				/* keyword */

				*type = WORDTYPE_KWORD;
				*out = *--p;

				return p;
			}

			/* it can only be a name */
			if (isspace(*p)) {
				/* end */
				*type = WORDTYPE_NAME;
				*out = i;

				return fbuffer;
			}

			if (!ischr(*p)) {
				if (!i) {
					*type = WORDTYPE_KWORD;
					*out = *p;

					return fbuffer;
				}

				if (ischr(p[-1])) {
					*type = WORDTYPE_NAME;
					*out = i;

					return p - i;
				}
			}
		}
	}

	/* there isn't a space at end of the line */

	if (in) 
		exit(generic_error("no '\"' found", linebuffer_save, lno));

	if (i == 1) {
		if (ischr(p[-1])) {
			*type = WORDTYPE_NAME;
			*out = 1;

			return p;
		}

		*type = WORDTYPE_KWORD;
		*out = *--p;

		return p;
	}

	if (!i) 
		return NULL;

	/* only can be a name */

	*type = WORDTYPE_NAME;
	*out = i;

	return fbuffer;
}


static inline int find_attr(char *attrname, const struct attr_table *tab) {
	int i;

	for (i = 0; tab[i].attrname; i++) 
		if (!strcmp(tab[i].attrname, attrname)) 
			return tab[i].attrmask;

	return 0;
}


int getattrmask(char *p, const struct attr_table *tab) {
	char *attrname;
	int type, out, mask = 0, t;

	if (!getword(p, &type, &out)) 
		return 0;

	if (type != WORDTYPE_KWORD) 
		exit(generic_error("non-attribute ('@') character after that", linebuffer_save, lno));

	if (out != '@') 
		exit(generic_error("not a '@' after string", linebuffer_save, lno));

	++p;
	_skip_spaces(p);

	while (getword(p, &type, &out)) {
		if (type != WORDTYPE_NAME) 
			exit(generic_error("an attribute name required", linebuffer_save, lno));

		attrname = strndup(p, out);

		if (!(t = find_attr(attrname, tab))) 
			exit(generic_error("unknown attribute", linebuffer_save, lno));

		mask |= t;

		free(attrname);
		_skip_word(p);
	}

	return mask;
}



static int parse_line(void) {
	char *p;
	int type, out;

	if ((p = getword(line_buffer, &type, &out)) >= line_eof) 
		/* empty line */
		return 0;

	if (type != WORDTYPE_KWORD && type != WORDTYPE_NAME) 
		exit(generic_error("line starts with a non-keyword", linebuffer_save, lno));

	return arch_do_keyword(p, arch_fn);
}


